//////////////////////////////////////////////////////////////////////////////////
//	 Copyright 2011 by Yosef Grabivker, ryuho@homenetworksoft.com
//
//   Licensed under the Apache License, Version 2.0 (the "License");
//   you may not use this file except in compliance with the License.
//   You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
//   Unless required by applicable law or agreed to in writing, software
//   distributed under the License is distributed on an "AS IS" BASIS,
//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//   See the License for the specific language governing permissions and
//   limitations under the License.
//////////////////////////////////////////////////////////////////////////////////

#include <wx/wx.h>
#include "../common/MarkupSTL.h"
#include <algorithm>
#include "guilogic.h"
#include "statecontroller.h"
#include "logicmanager.h"
#include "routercaps.h"

#define DEF_PAGE_SIZE		0x1000

#define RT_SIZE_MIN			40
#define RT_SIZE_MAX			800

#define NAT_SIZE_MIN		100
#define NAT_SIZE_MAX		1000

#define USAGE_GAP			20

#define SHRINK_MIN_TIME		30*60

//Do not decrease table size if router has this amount of memory or bigger in K
#define SHRINK_MIN_MEMORY	256

CStateController::CStateController(CGUILogic& logic):m_logic(logic)
{
	Reset();

	m_tCPUBootCount = 0;
	m_tCPUFrme = 0;
	m_tCPULoad = 0;
}

CStateController::~CStateController(void)
{
}

void CStateController::Reset()
{
	m_bSwap				 = false;
	m_bSentStartUpValues = false;
	m_bIs2_4Kernel		 = false;
	m_bIs2_2Kernel		 = false;
	m_bIs2_6Kernel		 = false;

	m_stNAT.tChange		 = 0;
	m_stRT.tChange		 = 0;

	m_stNATRealUsed		 = 0;
	m_stRTRealUsed		 = 0;
	m_bEnableRepair		 = false;
	m_nTryCount			 = 0;
}

void CStateController::OnCMD(const COMMANDRESPONSE& cmd, RTSTRING & strScipt, bool bEnabled, bool& bForced )
{
	bForced = m_bChanged = false;

	switch ( cmd.code )
	{
		case cCmdCPU:
		{
			m_tCPUFrme = cmd.lData;
			m_tCPULoad = cmd.lData1;
				
		}
		break;

		case cCmdGetVersion:
		{
			OnVersion(cmd.strData);
		}

		case cCmdMemMax:
		case cCmdMemVal:
		{
			OnMemory( cmd );

			if ( m_stMEM.IsValid() && !m_bSentStartUpValues )
			{
				DoStartUpActions(strScipt);

				m_bSentStartUpValues = true;
			}

			m_bChanged = true;
		}
		break;

		case  cCmdConntracMax:
		{
			OnConntracMax( cmd );
			m_bChanged = true;
		}
		break;

		case cCmdRTCacheMax:
		{
			OnRTCacheMax( cmd );
			m_bChanged = true;
		}
		break;

		case cCmdRTCacheVal:
		{
			m_stRTRealUsed = cmd.lData;
			m_bChanged = true;
		}
		break;


		case  cCmdGetSlabTable:
		{
			OnSlabTable( cmd );

			DoProcess(strScipt, bEnabled);

			if ( strScipt.empty() )
			{
				CheckNATCacheUsage(strScipt);

				bForced = strScipt.empty();
			}
			m_bChanged = true;
		}
		break;
	}
}

void CStateController::SetConntractRecordsCount(size_t stRecords)
{
	m_stNATRealUsed = stRecords;
}

//////////////////////////////////////////////////////////////////////////
#define MAX_TABLE_WASTE 1000
void CStateController::CheckNATCacheUsage(RTSTRING & strScipt)
{
	if ( m_bEnableRepair &&
		 CRouterCaps::GetIntstance().HasConnectionsList() )

	{
		if ( m_stNATRealUsed &&
			 m_stMEM.IsValid() && m_stRT.IsValid() && m_stNAT.IsValid()  )
		{
			if ( m_stNAT.stCur>m_stNATRealUsed &&
				 m_stNAT.stCur - m_stNATRealUsed>=MAX_TABLE_WASTE )
			{
				if (!m_nTryCount++)
				{
					wxString str;
					str+= wxString::Format( wxT("echo %d > "), m_stNAT.stObjperslab );
					str+=PATH_CONTRACK_MAX;
					str+=wxT(TELNET_ENTER);

					str+= wxString::Format( wxT("echo %d > "), m_stNAT.stCur );
					str+=PATH_CONTRACK_MAX;
					str+=wxT(TELNET_ENTER);

					strScipt = str.ToAscii();

					LOGENTRY entry;
					entry.strCustomEvent = _("NAT cache leak detected, try to repair.");
					m_logic.AddToLog(entry);

					m_stNAT.tRepairCount++;
					m_bChanged = true;
				}
				else
				{
					wxString str;
					str+= CMD_REBOOT;
					strScipt = str.ToAscii();
					strScipt+=TELNET_ENTER;

					LOGENTRY entry;
					entry.strCustomEvent = _("NAT cache leak detected, reboot.");
					m_logic.AddToLog(entry);

					m_stNAT.tBootCount++;
				}
			}
		}
	}
}

//////////////////////////////////////////////////////////////////////////
void CStateController::DoProcess(RTSTRING & strScipt, bool bEnabled)
{
	time_t tNow = time(NULL);

	if ( m_stMEM.IsValid() && m_stRT.IsValid() && m_stNAT.IsValid()  )
	{
		// 1. Routing table size 
		{
			size_t stUsage  = GetUsage( m_stRT );
			size_t stRawWanted = ((stUsage+USAGE_GAP)*m_stRT.GetTotal())/100;
			size_t stWanted = SlabOptimize( m_stRT, stRawWanted );

			stWanted = std::max<size_t>(stWanted, m_stRT.stObjperslab*RT_SIZE_MIN );
			size_t stSize = std::min<size_t>( m_stRT.stObjperslab*RT_SIZE_MAX, stWanted);

			size_t stMaxRaw = GetMaxPossible( m_stRT, m_stNAT, 0.5  );
			size_t stMax = SlabOptimize( m_stRT, stMaxRaw );

			if ( stMax<m_stRT.stObjperslab*RT_SIZE_MIN)
			{
				stMax = m_stRT.stObjperslab*RT_SIZE_MIN;
			}

			size_t stFinal = std::min<size_t>( stMax, stSize); 

			//Have plenty of free memory
			if ( stFinal<m_stRT.GetTotal() &&
				 m_stMEM.stMemFree > SHRINK_MIN_MEMORY )
			{
				goto NAT;
			}

			//decrease table size
			if ( stFinal<m_stRT.GetTotal() &&
				 tNow-m_stRT.tChange<SHRINK_MIN_TIME )
			{
				goto NAT;
			}

			if ( stFinal!=m_stRT.GetTotal() )
			{
				wxString str;
				str= wxString::Format( wxT("echo %d > "), stFinal);
				str+=CMD_RT_CACHE_TOTAL;

				strScipt = str.ToAscii();
				strScipt+=TELNET_ENTER; 

				m_stRT.UpdateChangeTime();

				if ( bEnabled )
				{
					m_stRT.tSizeChangeCount++;
					m_stRT.nSizeDelta = m_stRT.GetTotal()-stFinal;
				}

				return;
			}
		}

NAT:

		// 2. Routing table size 
		{
			size_t stUsage  = GetUsage( m_stNAT );
			size_t stRawWanted = ((stUsage+USAGE_GAP)*m_stNAT.GetTotal())/100;
			size_t stWanted = SlabOptimize( m_stNAT, stRawWanted );

			stWanted = std::max<size_t>(stWanted, m_stNAT.stObjperslab*NAT_SIZE_MIN );
			size_t stSize = std::min<size_t>( m_stNAT.stObjperslab*NAT_SIZE_MAX, stWanted); 
			
			size_t stMaxRaw = GetMaxPossible( m_stNAT, m_stRT, 0.9  );
			size_t stMax = SlabOptimize( m_stNAT, stMaxRaw );

			size_t stFinal = std::min<size_t>( stMax, stSize);

			//Does not have enough memory
			//Flush routing table
			if ( stFinal < stWanted &&
				 m_stRT.GetTotal()>m_stRT.stObjperslab*RT_SIZE_MIN )
				 
			{
				wxString str;
				str = wxT(CMD_FLUSH_RT_CACHE);
				strScipt+= str.ToAscii();
				strScipt+=TELNET_ENTER;

				if ( bEnabled )
				{
					m_stRT.tRepairCount++;
				}

				//Shrink it on next iteration
				m_stRT.tChange=0;
			}
			else
			{
				//Have plenty of free memory
				if ( stFinal<m_stNAT.GetTotal() &&
					m_stMEM.stMemFree > SHRINK_MIN_MEMORY )
				{
					return;
				}
		
				//decrease table size
				if ( stFinal<m_stNAT.GetTotal() &&
					tNow-m_stNAT.tChange<SHRINK_MIN_TIME )
				{
					return;
				}
			}

			if ( stFinal!=m_stNAT.GetTotal() )
			{
				wxString str;
				str+= wxString::Format( wxT("echo %d > "), stFinal);
				str+=PATH_CONTRACK_MAX;

				strScipt+= str.ToAscii();
				strScipt+=TELNET_ENTER; 

				m_stNAT.UpdateChangeTime();

				if ( bEnabled )
				{
					m_stNAT.tSizeChangeCount++;
					m_stNAT.nSizeDelta = m_stNAT.GetTotal()-stFinal;
				}
			}
		}
	}
}

void CStateController::DoStartUpActions(RTSTRING & strScipt)
{
	if ( !HaveSwap() && !m_bIs2_6Kernel )
	{
		strScipt = "echo '1'>/proc/sys/vm/bdflush";
		strScipt+=TELNET_ENTER;
	}
}


//////////////////////////////////////////////////////////////////////////
void CStateController::OnVersion(const RTSTRING& str)
{
	//Has avalanche
	m_bIs2_4Kernel = std::string::npos!=str.find("version 2.4");
	m_bIs2_2Kernel = std::string::npos!=str.find("version 2.2");
	m_bIs2_6Kernel = std::string::npos!=str.find("version 2.6");
}

/*
MemTotal:        13920 kB
MemFree:           364 kB
Buffers:          1088 kB
Cached:           6156 kB
SwapCached:          0 kB
Active:           3696 kB
Inactive:         5212 kB
HighTotal:           0 kB
HighFree:            0 kB
LowTotal:        13920 kB
LowFree:           364 kB
SwapTotal:           0 kB
SwapFree:            0 kB
Dirty:               0 kB
Writeback:           0 kB
Mapped:           4072 kB
Slab:             1524 kB
Committed_AS:     5128 kB
PageTables:        332 kB
VmallocTotal:  1048560 kB
VmallocUsed:      1292 kB
VmallocChunk:  1047004 kB
*/
void CStateController::OnMemory( const COMMANDRESPONSE& cmd )
{
	m_stMEM.stMemTotal = CLogicManager::ToNumberValIfLineHave(cmd.strData, CMD_MEM_TOTAL_NO_GRP_VAL);
	m_stMEM.stMemFree  = CLogicManager::ToNumberValIfLineHave(cmd.strData, CMD_MEM_FREE_NO_GRP_VAL);

	if ( CLogicManager::ToNumberValIfLineHave(cmd.strData, "SwapTotal") )
	{
		m_bSwap = true;
	}
	else
	{
		m_bSwap = false;
	}
}

void CStateController::OnConntracMax( const COMMANDRESPONSE& cmd )
{
	m_stNAT.SetTotal( cmd.lData );
}

void CStateController::OnRTCacheMax( const COMMANDRESPONSE& cmd )
{
	m_stRT.SetTotal( cmd.lData );
}

//read: /proc/slabinfo
//understanding after reading the source:
//understanding after reading the source:
//
//                  active-objects
//                  |      allocated-objects
//                  |      |         object-size
//                  |      |         |   active-slab-allocations
//                  |      |         |   |     total-slab-allocations
//                  |      |         |   |     |        alloc-size
//                  |      |         |   |     |        |
//inode_cache       423370 423556    512 60482 60508    1 :  248   62
//                                                           |     |
//                                                       limit     |
//                                                       batch-count
//# name            <active_objs> <num_objs> <objsize> <objperslab> <pagesperslab> : tunables <batchcount> <limit> <sharedfactor> : slabdata <active_slabs> <num_slabs> <sharedavail>
//ip_conntrack         973			1000		384		10				1 :			tunables   54			27		0 :				 slabdata    100			100      0
void CStateController::OnSlabTable( const COMMANDRESPONSE& cmd )
{
	PROCTABLEWITHHEADER table = CLogicManager::ParseTableWithHeaders( cmd.strData );

	if ( !table.headers.empty() )
	{
		for (size_t i=0;i<table.rows.size();++i )
		{
			TABLEROW& row = table.rows[i];

			if ( !row.empty() )
			{
				if ( row[0]==PATH_CONTRACK_NO_GRP_VAL )
				{
					if ( std::string::npos != table.headers[0].find("name") )
					{
						m_stNAT.stCur = CLogicManager::GeValFromTableInt(table, "<active_objs>", i, 0 );
						m_stNAT.stAllocated = CLogicManager::GeValFromTableInt(table, "<num_objs>", i, 0 );
						m_stNAT.stobjsize = CLogicManager::GeValFromTableInt(table, "<objsize>", i, 0 );
						m_stNAT.stObjperslab = CLogicManager::GeValFromTableInt(table, "<objperslab>", i, 0 );
						m_stNAT.stPagesperslab = CLogicManager::GeValFromTableInt(table, "<pagesperslab>", i, 0 );
					}
					else
					{
						m_stNAT.stCur = atol(row[1].c_str());
						m_stNAT.stAllocated = atol(row[2].c_str());
						m_stNAT.stobjsize = atol(row[3].c_str());

						if (m_stNAT.stobjsize)
						{
							m_stNAT.stObjperslab = DEF_PAGE_SIZE/m_stNAT.stobjsize;
						}

						m_stNAT.stPagesperslab = 1;
					}
				}

				if ( row[0]==CMD_RT_CACHE_CUR_NO_GRP_VAL )
				{
					if ( std::string::npos != table.headers[0].find("name") )
					{
						m_stRT.stCur = CLogicManager::GeValFromTableInt(table, "<active_objs>", i, 0 );
						m_stRT.stAllocated = CLogicManager::GeValFromTableInt(table, "<num_objs>", i, 0 );
						m_stRT.stobjsize = CLogicManager::GeValFromTableInt(table, "<objsize>", i, 0 );
						m_stRT.stObjperslab = CLogicManager::GeValFromTableInt(table, "<objperslab>", i, 0 );
						m_stRT.stPagesperslab = CLogicManager::GeValFromTableInt(table, "<pagesperslab>", i, 0 );
					}
					else
					{
						m_stRT.stCur = atol(row[1].c_str());
						m_stRT.stAllocated = atol(row[2].c_str());
						m_stRT.stobjsize = atol(row[3].c_str());

						if (m_stRT.stobjsize)
						{
							m_stRT.stObjperslab = DEF_PAGE_SIZE/m_stRT.stobjsize;
						}

						m_stRT.stPagesperslab = 1;
					}
				}
			}
		}
	}


	/*
	m_stNAT.stCur = CLogicManager::ToNumberValIfLineHave(cmd.strData, PATH_CONTRACK_NO_GRP_VAL);
	m_stNAT.stCur = CLogicManager::ToNumberValIfLineHave(cmd.strData, PATH_CONTRACK_NO_GRP_VAL);;
	m_stNAT.stObjperslab = ;
	m_stNAT.stPagesperslab = ;
	m_stNAT.stobjsize = ;


	

	m_stRT.stCur  = CLogicManager::ToNumberValIfLineHave(cmd.strData, CMD_RT_CACHE_CUR_NO_GRP_VAL);
	*/
}

size_t CStateController::GetUsage( const TableState& tb )
{
	size_t stUsage(0);
		
	if ( tb.IsValid() )
	{
		if ( tb.GetTotal()>tb.stCur )
		{
			stUsage = 100-(100*(tb.GetTotal()-tb.stCur))/tb.GetTotal();
		}
		else
		{
			stUsage = 100;
		}
	}

	return stUsage;
}


size_t CStateController::SlabOptimize( const TableState& tb, size_t stWanted )
{
	size_t stVal(0);

	if ( tb.IsValid() )
	{
		size_t stDif = stWanted%tb.stObjperslab;
		stVal = stDif==0?stWanted:(stWanted-stDif);
	}


	return stVal;
}

size_t	CStateController::TableState::GetMemNotAllocated() const
{
	size_t stRes(0);

	if ( IsValid() )
	{
		if ( stTotal>stAllocated )
		{
			stRes = (DEF_PAGE_SIZE*(stTotal-stAllocated))/stObjperslab;
		}
	}

	return stRes;
}


size_t CStateController::GetMaxPossible( const TableState& tb, const TableState& tbOther, double fMult/*=0.9*/ )
{
	size_t stRes(0);

	if ( m_stMEM.IsValid() && tb.IsValid() )
	{
		size_t stFree = fMult*m_stMEM.stMemFree*1024;
		size_t stOtherNeed = tbOther.GetMemNotAllocated();

		if ( stOtherNeed>stFree )
		{
			stFree = 0;
		}
		else
		{
			stFree-=stOtherNeed;
		}

		stRes = stFree/(DEF_PAGE_SIZE*tb.stPagesperslab);
		stRes*= tb.stObjperslab;
		stRes+= tb.stAllocated;
	}

	return stRes; 
}

void CStateController::OnFlushAction(cPanelType pt)
{
	switch ( pt )
	{
		case cPTNAT:
			++m_stNAT.tRepairCount;
		break;

		case cPTMem:
		break;

		case cPTRoute:
			++m_stRT.tRepairCount;
		break;

		case cPTCPU:
		break;

		default:
		break;
	}
}

void CStateController::OnRebootAction(cPanelType pt)
{
	switch ( pt )
	{
		case cPTNAT:
			++m_stNAT.tBootCount;
		break;

		case cPTMem:
			++m_stMEM.tBootCount;
		break;

		case cPTRoute:
			++m_stRT.tBootCount;
		break;

		case cPTCPU:
			++m_tCPUBootCount;
		break;

		default:
		break;
	}
}

std::string  CStateController::BuildStateXML()
{
	CMarkupSTL	doc;
	doc.AddElem( "state" );

	doc.AddChildElem( STATE_NAT_EL );

	if ( m_stNAT.GetTotal()>0 )
	{
		doc.AddChildAttrib( STATE_BOOT_CNT,			m_stNAT.tBootCount );
		doc.AddChildAttrib( STATE_REPAIR_CNT,		m_stNAT.tRepairCount );
		doc.AddChildAttrib( STATE_SIZECHANGE_CNT,	m_stNAT.tSizeChangeCount );
		doc.AddChildAttrib( STATE_TOTAL_CNT,		m_stNAT.GetTotal() );
		doc.AddChildAttrib( STATE_USED_CNT,			m_stNAT.stCur );
		doc.AddChildAttrib( STATE_ALLOCTD_CNT,		m_stNAT.stAllocated );

		size_t stLeak(0);

		if ( CRouterCaps::GetIntstance().HasConnectionsList() )
		{
			if ( m_stNAT.stCur>m_stNATRealUsed )
			{
				stLeak = m_stNAT.stCur - m_stNATRealUsed;
			}
		}

		doc.AddChildAttrib( STATE_LEAK_CNT,	stLeak );
	}
	else
	{
		doc.AddChildAttrib( STATE_BOOT_CNT,			wxString(_("n/a")).ToAscii() );
		doc.AddChildAttrib( STATE_REPAIR_CNT,		wxString(_("n/a")).ToAscii() );
		doc.AddChildAttrib( STATE_SIZECHANGE_CNT,	wxString(_("n/a")).ToAscii() );
		doc.AddChildAttrib( STATE_TOTAL_CNT,		wxString(_("n/a")).ToAscii() );
		doc.AddChildAttrib( STATE_USED_CNT,			wxString(_("n/a")).ToAscii() );
		doc.AddChildAttrib( STATE_ALLOCTD_CNT,		wxString(_("n/a")).ToAscii() );
		doc.AddChildAttrib( STATE_LEAK_CNT,			wxString(_("n/a")).ToAscii() );
	}

	doc.AddChildElem( STATE_RT_EL );

	doc.AddChildAttrib( STATE_BOOT_CNT,			m_stRT.tBootCount );
	doc.AddChildAttrib( STATE_REPAIR_CNT,		m_stRT.tRepairCount );
	doc.AddChildAttrib( STATE_SIZECHANGE_CNT,	m_stRT.tSizeChangeCount );
	doc.AddChildAttrib( STATE_TOTAL_CNT,		m_stRT.GetTotal() );
	doc.AddChildAttrib( STATE_USED_CNT,			m_stRT.stCur );
	doc.AddChildAttrib( STATE_ALLOCTD_CNT,		m_stRT.stAllocated );

	{
		size_t stLeak(0);

		/*
		if ( m_stRT.stCur>m_stRTRealUsed )
		{
			stLeak = m_stRT.stCur - m_stRTRealUsed;
		}*/

		doc.AddChildAttrib( STATE_LEAK_CNT,	stLeak );
	}

	doc.AddChildElem( STATE_MEM_EL );
	doc.AddChildAttrib( STATE_BOOT_CNT,			m_stMEM.tBootCount );
	doc.AddChildAttrib( STATE_TOTAL_CNT,		m_stMEM.stMemTotal );
	doc.AddChildAttrib( STATE_USED_CNT,			m_stMEM.stMemTotal-m_stMEM.stMemFree );
	doc.AddChildAttrib( STATE_FREE,				m_stMEM.stMemFree );

	wxString str = wxString::Format( wxT("%2.2d %%"), m_stMEM.stMemTotal?(100*(m_stMEM.stMemTotal-m_stMEM.stMemFree))/m_stMEM.stMemTotal:0 );
	doc.AddChildAttrib( STATE_USAGE,			str.ToAscii()  );

	doc.AddChildElem( STATE_CPU_EL );
	doc.AddChildAttrib( STATE_BOOT_CNT,			m_tCPUBootCount );
	doc.AddChildAttrib( STATE_TOTAL_CNT,		m_tCPUFrme );
	doc.AddChildAttrib( STATE_USED_CNT,			m_tCPULoad );

	return doc.GetDoc();
}
